package com.yinuo;
import com.arcsoft.face.Face3DAngle;
import com.arcsoft.face.FaceInfo;
import com.arcsoft.face.Rect;
import com.arcsoft.face.toolkit.ImageFactory;
import com.arcsoft.face.toolkit.ImageInfo;
import com.github.sarxos.webcam.Webcam;
import com.github.sarxos.webcam.WebcamPanel;
import com.github.sarxos.webcam.util.ImageUtils;
import com.google.common.base.Strings;
import com.google.common.util.concurrent.*;
import com.yinuo.face.ArcSoftFaceEngine;
import javafx.application.Platform;
import javafx.embed.swing.SwingFXUtils;
import javafx.embed.swing.SwingNode;
import javafx.event.ActionEvent;
import javafx.fxml.Initializable;
import javafx.scene.control.Alert;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextField;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.audio.AudioPlayer;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.Optional;
import java.util.ResourceBundle;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

public class FaceInputController implements Initializable, WebcamPanel.Painter {

    public static final Logger logger = LoggerFactory.getLogger(FaceInputController.class);
    public static final String AUDIO_9360_WAV = "/audio/9360.wav";
    public static final String AUDIO_5756_WAV = "/audio/5756.wav";

    public Label tip;
    public Button register;
    public ImageView saveImg;
    public TextField saveID;
    public TextField saveName;
    public BorderPane video;
    public Button saveBtn;
    public Button cancelBtn;
    private WebcamPanel.Painter defaultPainter;
    private ArcSoftFaceEngine arcSoftFaceEngine;
    private String tipMsg = "";

    private boolean isRegister = false;
    private int currentRightTimes = 0;
    private final int keepRightTimes = 5;
    private Webcam webcam;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        logger.info("开始启动");

        new Thread(()->{
            Uninterruptibles.sleepUninterruptibly(1,TimeUnit.SECONDS);
            arcSoftFaceEngine = new ArcSoftFaceEngine();
            arcSoftFaceEngine.init();

            webcam = Webcam.getDefault();
            webcam.setViewSize(new Dimension(640, 480));

            Platform.runLater(()->{
                WebcamPanel webcamPanel = new WebcamPanel(webcam, true);
                webcamPanel.setMirrored(true);
                defaultPainter = webcamPanel.getDefaultPainter();
                webcamPanel.setPainter(FaceInputController.this);
                SwingNode swingNode = new SwingNode();
                swingNode.setContent(webcamPanel);
                video.setCenter(swingNode);
            });
        }).start();

        saveID.setOnKeyReleased(event -> validBtn());
        saveName.setOnKeyReleased(event -> validBtn());
        logger.info("启动成功");
    }

    public void register(ActionEvent actionEvent) {
        playAudio(AUDIO_9360_WAV);
        this.currentRightTimes = 0;
        this.tipMsg = "";
        this.isRegister = true;
    }

    public void save(ActionEvent actionEvent) {
        try {
            String fileName = saveID.getText() + "_" + saveName.getText();
            ImageIO.write(SwingFXUtils.fromFXImage(saveImg.getImage(), null),"jpg",new File(fileName + ".jpg"));
            showMessage(Alert.AlertType.INFORMATION, "保存成功");
            cancel(actionEvent);
        } catch (IOException e) {
            showMessage(Alert.AlertType.ERROR, "保存失败");
        }
    }

    private void showMessage(Alert.AlertType information, String headerText) {
        Alert alert = new Alert(information);
        alert.setHeaderText(headerText);
        alert.show();
    }

    public void cancel(ActionEvent actionEvent) {
        saveImg.setImage(null);
        saveID.setText("");
        saveName.setText("");
        validBtn();
    }

    public void validBtn() {
        boolean valid = Strings.isNullOrEmpty(saveID.getText()) || Strings.isNullOrEmpty(saveName.getText()) || saveImg.getImage() == null;
        cancelBtn.setDisable(valid);
        saveBtn.setDisable(valid);
    }

    @Override
    public void paintPanel(WebcamPanel webcamPanel, Graphics2D graphics2D) {
        defaultPainter.paintPanel(webcamPanel, graphics2D);
    }

    @Override
    public void paintImage(WebcamPanel webcamPanel, BufferedImage bufferedImage, Graphics2D graphics2D) {
        if (!isRegister) {
            Platform.runLater(() -> tip.setText(this.tipMsg));
            defaultPainter.paintImage(webcamPanel, bufferedImage, graphics2D);
            return;
        }

        Graphics2D graphics = bufferedImage.createGraphics();
        try {
            int width = bufferedImage.getWidth();
            int height = bufferedImage.getHeight();
            int paddingWidth = width / 4;
            int paddingHeight = height / 6;

            int drawRectWidth = width - paddingWidth * 2;
            int drawRectHeight = height - paddingHeight * 2;

            drawRegisterRect(graphics, width, height, paddingWidth, paddingHeight, drawRectWidth, drawRectHeight);

            byte[] bytes = ImageUtils.toByteArray(bufferedImage, ImageUtils.FORMAT_JPG);
            ImageInfo imageInfo = ImageFactory.getRGBData(bytes);
            Optional<FaceInfo> detect = arcSoftFaceEngine.detect(imageInfo);
            detect.ifPresent(p -> {
                this.tipMsg = "";
                Rect faceRect = p.getRect();
                logger.debug("检测 x:{} y:{} width:{} height:{}", faceRect.left, faceRect.top, faceRect.right - faceRect.left, faceRect.bottom - faceRect.top);
                //检测人脸位置是否在预定区域内
                this.tipMsg = parseRect(paddingWidth, paddingHeight, drawRectWidth, drawRectHeight, faceRect);

                //检测人脸的俯仰角度
                if (Strings.isNullOrEmpty(this.tipMsg)) {
                    Optional<Face3DAngle> face3DAngle = arcSoftFaceEngine.face3D(imageInfo);
                    face3DAngle.ifPresent(face3D -> this.tipMsg = parseFace3DAngle(face3D));
                }

                boolean empty = Strings.isNullOrEmpty(this.tipMsg);
                graphics.setColor(empty ? Color.green : Color.red);
                if (!empty) {
                    currentRightTimes = 0;
                } else if (currentRightTimes < keepRightTimes) {
                    playAudio(AUDIO_9360_WAV);
                    this.tipMsg = "请操持姿势不要动 " + (keepRightTimes - currentRightTimes);
                    currentRightTimes++;
                } else {
                    this.tipMsg = "己采集人脸，请完善详细资料";
                    this.isRegister = false;
                    Platform.runLater(() -> saveImg.setImage(SwingFXUtils.toFXImage(webcam.getImage(), null)));
                    playAudio(AUDIO_5756_WAV);
                }

                graphics.drawRect(faceRect.left, faceRect.top, faceRect.right - faceRect.left, faceRect.bottom - faceRect.top);
                Platform.runLater(() -> tip.setText(this.tipMsg));
            });
        } catch (Exception e) {
            logger.error("检测人脸出错", e);
        } finally {
            graphics.dispose();
        }

        defaultPainter.paintImage(webcamPanel, bufferedImage, graphics2D);
    }

    private void playAudio(String name) {
        AudioPlayer.player.start(getClass().getResourceAsStream(name));
    }

    private void drawRegisterRect(Graphics2D graphics, int width, int height, int paddingWidth, int paddingHeight, int drawRectWidth, int drawRectHeight) {
        graphics.setColor(Color.BLUE);
        //画左右两条边线
        graphics.drawLine(0, height / 2, paddingWidth, height / 2);
        graphics.drawLine(width - paddingWidth, height / 2, width, height / 2);
        //画上下两条边线
        graphics.drawLine(width / 2, 0, width / 2, paddingHeight);
        graphics.drawLine(width / 2, height - paddingHeight, width / 2, height);
        logger.debug("预定 x:{} y:{} width:{} height:{}", paddingWidth, paddingHeight, drawRectWidth, drawRectHeight);
        //画中间预定人脸位置框
        graphics.drawRect(paddingWidth, paddingHeight, drawRectWidth, drawRectHeight);
    }

    private String parseRect(int paddingWidth, int paddingHeight, int drawRectWidth, int drawRectHeight, Rect faceRect) {
        int faceRectWidth = faceRect.right - faceRect.left;
        int faceRectHeight = faceRect.bottom - faceRect.top;
        if (Strings.isNullOrEmpty(this.tipMsg) && (faceRectHeight > drawRectHeight || faceRectWidth > drawRectWidth)) {
            return "人脸太大，请稍微后退一点";
        }
        if (Strings.isNullOrEmpty(this.tipMsg) && (faceRectWidth < drawRectWidth * 2 / 3 || faceRectHeight < drawRectHeight * 2 / 3)) {
            return "人脸太小，请稍微靠近一点";
        }
        if (Strings.isNullOrEmpty(this.tipMsg) && (faceRect.top < paddingHeight || faceRect.bottom > drawRectHeight + paddingHeight)) {
            return "人脸己超出预定上下边界";
        }
        if (Strings.isNullOrEmpty(this.tipMsg) && (faceRect.left < paddingWidth || faceRect.right > drawRectWidth + paddingWidth)) {
            return "人脸己超出预定左右边界";
        }
        return "";
    }

    private String parseFace3DAngle(Face3DAngle f) {
        logger.debug("face3DAngle:{}", f);
        if (f.getPitch() > 5) {
            return "人脸仰视角度太大，请稍微低头";
        } else if (f.getPitch() < -5) {
            return "人脸俯视角度太大，请稍微抬头";
        } else if (f.getYaw() > 5) {
            return "人脸俯左转角度太大";
        } else if (f.getYaw() < -5) {
            return "人脸右转角度太大";
        } else if (Math.abs(f.getRoll()) > 5) {
            return "请端正姿势";
        }
        return "";
    }
}
